Youve probably seen them buried deep inside a map file: an identifier that kind of looks like one of your function names after being run through a blender.
If the following function appears in a C source file,
void StraightCFunction (int* pInput, const char* pszOutput)
the following symbol appears in the object and map files:
_StraightCFunction
This function is suitable for inclusion in the module definition file. Take the same function and insert it into a CPP source file, and you get the following:
?StraightCFunction@@YAXPAHPBD@Z
Whats the stuff following the name? This is compiler-speak for the argument list and return types. In standard C, it is illegal to have two functions or variables with the same name (unless one or both are static scope). So the compiler merely refers to the symbol by its textual name, with an underscore prepended. C++, however, supports function overloading. Function overloading enables you to have two externally visible functions with the same name, provided their argument lists are different. Thus, in a C++ file, you could have the following:
int Addition (int nOperand1, int nOperand2); long Addition (long lOperand1, long lOperand2);
To avoid name conflicts, and to ensure that the linker enforces strong typing, the compiler encodes (mangles) the parameter types, return type, and any qualifiers (such as const) into a single name.
This phenomenon is what leads to object and library incompatibilities between compiler vendors because each vendor uses a slightly different encoding scheme. And although compiler geeks try to put a positive spin on it (they call it name decoration), it complicates the DLL development process immensely. Listing 33.1 shows a sample module definition file that includes compiler-mangled names.
Listing 33.1 Sample Module Definition File
; ; Sample Module Defintion File ; LIBRARY afxsamp1 DESCRIPTION Afx Sample Windows Dynamic Link Library EXPORTS ?StraightCPPFunction@@YAXPAHPBD@Z @1000 NONAME ; Exported by ordinal StraightCFunction @1001 NONAME ; Exported by ordinal ?Addition@@YAHHH@Z ; Exported by name ?Addition@@YAJJJ@Z ; Exported by name
In this example, the StraightCFunction and StraightCPPFunction are exported by ordinal. The @xxxx assigns a unique numeric value to each identifier; the NONAME qualifier explicitly removes the textual name from the executable files. The two versions of Addition are exported by name; each identifier will be embedded in each executable file referencing it.
Where do you find the names to be inserted in the module-definition file? In the linker-generated map file.
Unfortunately, after doing a moderate amount of work with module definition files, youll find yourself reading mangled names in their native form. Its a bittersweet day in any developers life.
There is an alternative to mangled name madness. Microsoft provides a keyword to publish the contents of an entire class. However, its the least efficient way to export your member functions, and it carries all of the performance hits discussed previously (bloated executables and longer load time).
Class exporting is done by embedding a declaration specification between the keyword class and the name of the class (see Listing 33.2). Using the export directive __declspec (dllexport) on the class declaration tells the compiler and linker that all member functionspublic, protected, and privateshould be published in the import file.
Listing 33.2 Exporting Class by Class
class __declspec (dllexport) CMyWindow: public CWnd
{
CMyWindow();
~CMyWindow();
// Generated message map functions
protected:
//{{AFX_MSG(CMyWindow)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
It makes little sense, however, to publish all membersfor example, private members can only be referenced by other members of your class and friend classes. Unless a friend class exists outside your DLL (a dubious design decision), this is a waste of an export, and a decrease in performance. Avoid class exporting in all but the simplest of cases.
So far, Ive only talked about exporting from DLLs. Consider the perspective of executables using your DLL. Rather than exporting identifiers, they will be importing them.
These executables should see your sample class as
class __declspec (dllimport) CMyWindow: public CWnd
As the DLL developer, you are left with one of two options: Maintain a duplicate copy of the class header file using __declspec (dllimport) (not a good long-term solution), or use the preprocessor to present the compiler with what it expects to see.
To use the preprocessor, define a special symbol in the project settings of your extension DLL: MY_DLL_INTERNALS. Then, in each header file, use the following construct:
#ifdef MY_DLL_INTERNALS #undef EXPORTMODE #define EXPORTMODE __declspec (dllexport) // Export the identifiers #else #undef EXPORTMODE #define EXPORTMODE __declspec (dllimport) // Import the identifiers #endif
Then modify each class declaration to use the following form:
class EXPORTMODE CMyWindow: public CWnd
When the include file is read during the extension DLL compile, MY_DLL_INTERNALS will be defined, and the class will be exported. But when users of your DLL compile, MY_DLL_INTERNALS will be undefined and the class will be imported. This trick can also be used to export functions and data members.
You can cause a function to be exported by explicitly including the __declspec (dllexport) keyword on both its declaration and definition. This directs the compiler and linker to export the function in the import library. Unfortunately, the function is exported by name, not ordinal. You can override the export in the module definition file, but, if youre going to the trouble of looking up the mangled name anyway, why bother using the export keyword? Listing 33.3 contains an example of exporting member by member.
Listing 33.3 Exporting Member by Member
// Declaration of class
class CMyClass : public CObject
{
public:
EXPORTMODE CMyClass();
private:
void DoSomething (int, void*);
};
// Implementation of members
EXPORTMODE CMyClass::CMyClass()
{
...
}
void CMyClass::DoSomething (int nValue, void* pData)
{
}
Should you forget to reset EXPORTMODE to __declspec (dllexport) in the implementation (CPP) files, the compiler will let you know in a hurry. __declspec (dllimport) tells the compiler that the actual function will be provided externally; when the compiler sees an actual definition, it knows something funky is going on.
Most AFX DLLs only export functions and classes. But sometimes it is important to expose a data member to a caller. Much like functions, data members can be exported and imported using the __declspec (dllexport) and __declspec (dllimport) keywords.
EXPORTMODE CString g_strPublic;